package fw.asmx.launcher.ui;

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.view.*;
import android.widget.Scroller;
import fw.asmx.launcher.R;

public class TileLayout extends ViewGroup
{
    //填充
    private int layoutWidth;

    private int tileSpacing;

    private int smallTileWidth;
    private int normalTileWidth;
    private int wideTileWidth;

    private int areaNormalWidth;

    private int p1 = 0;
    private int p2;
    private int p3;
    private int p4;
    private int p5;

    private boolean isNewLine = false;

    private int allChildViewHeight = 0;

    //滚动
    private static final int TOUCH_STATE_REST = 0;
    private static final int TOUCH_STATE_SCROLLING = 1;

    private VelocityTracker vt = null;
    private float lastMotionY;
    private int movDsts;
    private int maxMov = 0;
    private Scroller sclr;
    private int upExDsts;
    private int dnExDsts;
    private int tchSlop;
    private int tchStat = TOUCH_STATE_REST;

    //双击
    private DoubleClickHander dch;

    public interface OnTileClickListener
    {
        public void onTileClick(TileView v);
    }

    public interface OnTileLongClickListener
    {
        public boolean onTileLongClick(TileView v);
    }

    public TileLayout(Context context)
    {
        super(context);
        construction(context);
    }

    public TileLayout(Context context, AttributeSet attrs)
    {
        super(context, attrs);
        construction(context);
    }

    public TileLayout(Context context, AttributeSet attrs, int defStyleAttr)
    {
        super(context, attrs, defStyleAttr);
        construction(context);
    }

    private void construction(Context context)
    {
        sclr = new Scroller(context);
        final ViewConfiguration vc = ViewConfiguration.get(context);
        tchSlop = vc.getScaledTouchSlop();
    }

    public void addTile(Drawable icon, String appName, String pkgName, int tileSize)
    {
        LayoutInflater li =
                (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        TileView mv = (TileView) li.inflate(R.layout.view_applstitem, null);
        mv.initView(icon, appName, pkgName, tileSize);
        addView(mv);
    }

    private void init()
    {
        layoutWidth = getWidth();
        int spacing = dp2px(10);
        smallTileWidth = (layoutWidth - spacing * 3) / 4;
        normalTileWidth = (layoutWidth - spacing) / 2;
        wideTileWidth = layoutWidth;
        areaNormalWidth = normalTileWidth + spacing;
        tileSpacing = spacing;
    }

    private int dp2px(int dp)
    {
        return (int) (getResources().getDisplayMetrics().density * dp + 0.5F);
    }

    public void setOnTileClickListener(final OnTileClickListener onTileClickListener)
    {
        int cnt = getChildCount();
        for (int i = 0; i < cnt; i++)
        {
            final TileView mv = (TileView) getChildAt(i);
            mv.setOnClickListener(new OnClickListener()
            {
                @Override public void onClick(View v)
                {
                    onTileClickListener.onTileClick(mv);
                }
            });
        }
    }

    class DoubleClickHander extends Handler
    {
        DoubleClickHander()
        {
        }

        @Override public void handleMessage(Message msg)
        {
            getChildAt(msg.what).callOnClick();
        }
    }

    public void setOnTileLongClickListener(final OnTileLongClickListener onTileLongClickListener)
    {
        int cnt = getChildCount();
        for (int i = 0; i < cnt; i++)
        {
            final TileView mv = (TileView) getChildAt(i);
            mv.setOnLongClickListener(new OnLongClickListener()
            {
                @Override public boolean onLongClick(View v)
                {
                    return onTileLongClickListener.onTileLongClick(mv);
                }
            });
        }
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        measureChildren(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec));
        init();
    }

    private int measureWidth(int pWidthMeasureSpec)
    {
        int result = 0;
        int widthMode = MeasureSpec.getMode(pWidthMeasureSpec);// 得到模式
        int widthSize = MeasureSpec.getSize(pWidthMeasureSpec);// 得到尺寸

        switch (widthMode)
        {
            case MeasureSpec.AT_MOST:
            case MeasureSpec.EXACTLY:
                result = widthSize;
                break;
        }
        return result;
    }

    private int measureHeight(int pHeightMeasureSpec)
    {
        int result = 0;

        int heightMode = MeasureSpec.getMode(pHeightMeasureSpec);
        int heightSize = MeasureSpec.getSize(pHeightMeasureSpec);

        switch (heightMode)
        {
            case MeasureSpec.AT_MOST:
            case MeasureSpec.EXACTLY:
                result = heightSize;
                break;
        }
        return result;
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b)
    {
        if (layoutWidth == 0)
        {
            return;
        }
        p2 = smallTileWidth + tileSpacing;
        p3 = 2 * p2;
        p4 = 3 * p2;
        p5 = 4 * p2;

        int curMaxHeight = smallTileWidth;

        int curWidth = 0;
        int curHeight = 0;

        int mainArea = 1;
        int subArea = 1;

        int cnt = getChildCount();
        for (int i = 0; i < cnt; i++)
        {
            //get sub view's width and height
            final TileView mv = (TileView) getChildAt(i);
            int measuredHeight = mv.getMeasuredHeight();
            int measuredWidth = mv.getMeasuredWidth();
            //
            //set curMaxHeight
            if (measuredHeight > curMaxHeight)
            {
                curMaxHeight = measuredHeight;
            }
            //fill layout
            mv.layout(curWidth, curHeight, curWidth + measuredWidth, curHeight + measuredHeight);
            if (i + 1 == cnt)
            {
                maxMov = curHeight + normalTileWidth - getHeight();
                break;
            }
            //get next view to get its Width
            TileView nmv = (TileView) getChildAt(i + 1);
            //根据当前Area，当前填充磁贴尺寸判断下一个可以填充的位置
            //ensure next can fill area
            int[] nextCanFillArea =
                    ensureNextCanFillArea(mainArea, subArea, measuredWidth);
            //下一个可以填充的Area，下一个可以填充的curWidth，当前的curHeight，下一个磁贴的宽度
            int[] nextLoc =
                    adjustNextTileLoc(nextCanFillArea[0], nextCanFillArea[1], nmv.getMeasuredWidth());

            curWidth = nextLoc[0];
            curHeight = nextLoc[1];
            mainArea = nextLoc[2];
            subArea = nextLoc[3];

            if (isNewLine)
            {
                curMaxHeight = smallTileWidth;
                isNewLine = false;
            }
        }
    }

    //mainArea,subArea
    private int[] ensureNextCanFillArea(int curMainArea, int curSubArea, int curTileWidth)
    {
        int[] area = new int[2];
        switch (curMainArea % 2)
        {
            case 1:
                switch (curSubArea)
                {
                    case 1:
                        if (curTileWidth == smallTileWidth)
                        {
                            area[0] = curMainArea;
                            area[1] = 2;
                        }
                        else if (curTileWidth == normalTileWidth)
                        {
                            area[0] = curMainArea + 1;
                            area[1] = 1;
                        }
                        else if (curTileWidth == wideTileWidth)
                        {
                            area[0] = curMainArea + 2;
                            area[1] = 1;
                        }
                        break;
                    case 2:
                        if (curTileWidth == smallTileWidth)
                        {
                            area[0] = curMainArea;
                            area[1] = 3;
                        }
                        break;
                    case 3:
                        if (curTileWidth == smallTileWidth)
                        {
                            area[0] = curMainArea;
                            area[1] = 4;
                        }
                        break;
                    case 4:
                        if (curTileWidth == smallTileWidth)
                        {
                            area[0] = curMainArea + 1;
                            area[1] = 1;
                        }
                        break;
                }
                break;
            case 0:
                switch (curSubArea)
                {
                    case 1:
                        if (curTileWidth == smallTileWidth)
                        {
                            area[0] = curMainArea;
                            area[1] = 2;
                        }
                        else if (curTileWidth == normalTileWidth)
                        {
                            area[0] = curMainArea + 1;
                            area[1] = 1;
                        }
                        break;
                    case 2:
                        if (curTileWidth == smallTileWidth)
                        {
                            area[0] = curMainArea;
                            area[1] = 3;
                        }
                        break;
                    case 3:
                        if (curTileWidth == smallTileWidth)
                        {
                            area[0] = curMainArea;
                            area[1] = 4;
                        }
                        break;
                    case 4:
                        if (curTileWidth == smallTileWidth)
                        {
                            area[0] = curMainArea + 1;
                            area[1] = 1;
                        }
                        break;
                }
                break;
        }
        return area;
    }

    //curWidth,curHeight,mainArea,subArea
    private int[] adjustNextTileLoc(int mainArea, int subArea, int newTileWidth)
    {
        int[] loc = new int[4];
        switch (mainArea % 2)
        {
            case 1://left
                switch (subArea)
                {
                    case 1:
                        loc[0] = p1;
                        loc[1] = ((mainArea + 1) / 2 - 1) * areaNormalWidth;
                        loc[2] = mainArea;
                        loc[3] = 1;
                        break;
                    case 2:
                        if (newTileWidth == smallTileWidth)
                        {
                            loc[0] = p2;
                            loc[1] = ((mainArea + 1) / 2 - 1) * areaNormalWidth;
                            loc[2] = mainArea;
                            loc[3] = 2;
                        }
                        else if (newTileWidth == normalTileWidth)
                        {
                            loc[0] = p3;
                            loc[1] = ((mainArea + 1) / 2 - 1) * areaNormalWidth;
                            loc[2] = mainArea + 1;
                            loc[3] = 1;
                        }
                        else if (newTileWidth == wideTileWidth)
                        {
                            loc[0] = p1;
                            loc[1] = ((mainArea + 1) / 2 - 1) * areaNormalWidth +
                                     normalTileWidth +
                                     tileSpacing;
                            loc[2] = mainArea + 2;
                            loc[3] = 1;
                            isNewLine = true;
                        }
                        break;
                    case 3:
                        if (newTileWidth == smallTileWidth)
                        {
                            loc[0] = p1;
                            loc[1] = ((mainArea + 1) / 2 - 1) * areaNormalWidth + smallTileWidth +
                                     tileSpacing;
                            loc[2] = mainArea;
                            loc[3] = 3;
                        }
                        else if (newTileWidth == normalTileWidth)
                        {
                            loc[0] = p3;
                            loc[1] = ((mainArea + 1) / 2 - 1) * areaNormalWidth;
                            loc[2] = mainArea + 1;
                            loc[3] = 1;
                        }
                        else if (newTileWidth == wideTileWidth)
                        {
                            loc[0] = p1;
                            loc[1] = ((mainArea + 1) / 2 - 1) * areaNormalWidth +
                                     normalTileWidth +
                                     tileSpacing;

                            loc[2] = mainArea + 2;
                            loc[3] = 1;
                            isNewLine = true;
                        }
                        break;
                    case 4:
                        if (newTileWidth == smallTileWidth)
                        {
                            loc[0] = p2;
                            loc[1] = ((mainArea + 1) / 2 - 1) * areaNormalWidth + smallTileWidth +
                                     tileSpacing;
                            loc[2] = mainArea;
                            loc[3] = 4;
                        }
                        else if (newTileWidth == normalTileWidth)
                        {
                            loc[0] = p3;
                            loc[1] = ((mainArea + 1) / 2 - 1) * areaNormalWidth;
                            loc[2] = mainArea + 1;
                            loc[3] = 1;
                        }
                        else if (newTileWidth == wideTileWidth)
                        {
                            loc[0] = p1;
                            loc[1] = ((mainArea + 1) / 2 - 1) * areaNormalWidth +
                                     normalTileWidth +
                                     tileSpacing;

                            loc[2] = mainArea + 2;
                            loc[3] = 1;
                            isNewLine = true;
                        }
                        break;
                }
                break;
            case 0://right
                switch (subArea)
                {
                    case 1:
                        if (newTileWidth == smallTileWidth || newTileWidth == normalTileWidth)
                        {
                            loc[0] = p3;
                            loc[1] = (mainArea / 2 - 1) * areaNormalWidth;
                            loc[2] = mainArea;
                            loc[3] = 1;
                        }
                        else if (newTileWidth == wideTileWidth)
                        {
                            loc[0] = p1;
                            loc[1] = (mainArea / 2 - 1) * areaNormalWidth + normalTileWidth +
                                     tileSpacing;

                            loc[2] = mainArea + 1;
                            loc[3] = 1;
                            isNewLine = true;
                        }
                        break;
                    case 2:
                        if (newTileWidth == smallTileWidth)
                        {
                            loc[0] = p4;
                            loc[1] = (mainArea / 2 - 1) * areaNormalWidth;
                            loc[2] = mainArea;
                            loc[3] = 2;
                        }
                        else if (newTileWidth == normalTileWidth || newTileWidth == wideTileWidth)
                        {
                            loc[0] = p1;
                            loc[1] = (mainArea / 2 - 1) * areaNormalWidth + normalTileWidth +
                                     tileSpacing;

                            loc[2] = mainArea + 1;
                            loc[3] = 1;
                            isNewLine = true;
                        }
                        break;
                    case 3:
                        if (newTileWidth == smallTileWidth)
                        {
                            loc[0] = p3;
                            loc[1] =
                                    (mainArea / 2 - 1) * areaNormalWidth + smallTileWidth +
                                    tileSpacing;
                            loc[2] = mainArea;
                            loc[3] = 3;
                        }
                        else if (newTileWidth == normalTileWidth || newTileWidth == wideTileWidth)
                        {
                            loc[0] = p1;
                            loc[1] = (mainArea / 2 - 1) * areaNormalWidth + normalTileWidth +
                                     tileSpacing;

                            loc[2] = mainArea + 1;
                            loc[3] = 1;
                            isNewLine = true;
                        }
                        break;
                    case 4:
                        if (newTileWidth == smallTileWidth)
                        {
                            loc[0] = p4;
                            loc[1] =
                                    (mainArea / 2 - 1) * areaNormalWidth + smallTileWidth +
                                    tileSpacing;
                            loc[2] = mainArea;
                            loc[3] = 4;
                        }
                        else if (newTileWidth == normalTileWidth || newTileWidth == wideTileWidth)
                        {
                            loc[0] = p1;
                            loc[1] += (mainArea / 2 - 1) * areaNormalWidth + normalTileWidth +
                                      tileSpacing;

                            loc[2] = mainArea + 1;
                            loc[3] = 1;
                            isNewLine = true;
                        }
                        break;
                }
                break;
        }
        return loc;
    }

    public int getLayoutWidth()
    {
        return layoutWidth;
    }

    public int getTileSpacing()
    {
        return tileSpacing;
    }

    @Override
    public void computeScroll()
    {
        if (sclr.computeScrollOffset())
        {
            scrollTo(0, sclr.getCurrY());
            postInvalidate();
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev)
    {
        final int action = ev.getAction();
        final float curY = ev.getY();
        switch (action)
        {
            case MotionEvent.ACTION_DOWN:
                lastMotionY = curY;
                tchStat = sclr.isFinished() ? TOUCH_STATE_REST : TOUCH_STATE_SCROLLING;
                break;
            case MotionEvent.ACTION_MOVE:
                final int yDiff = (int) Math.abs(lastMotionY - curY);
                if (yDiff > tchSlop)
                {
                    tchStat = TOUCH_STATE_SCROLLING;
                }
                break;
            case MotionEvent.ACTION_CANCEL:
            case MotionEvent.ACTION_UP:
                tchStat = TOUCH_STATE_REST;
                break;
        }
        return tchStat != TOUCH_STATE_REST;
    }


    @Override
    public boolean onTouchEvent(MotionEvent ev)
    {
        //super.onTouchEvent(ev);
        if (vt == null)
        {
            vt = VelocityTracker.obtain();
        }
        vt.addMovement(ev);

        final float curY = ev.getY();
        switch (ev.getAction())
        {
            case MotionEvent.ACTION_DOWN:
                if (!sclr.isFinished())
                {
                    sclr.forceFinished(true);
                    movDsts = sclr.getCurrY();
                }
                lastMotionY = curY;
                break;
            case MotionEvent.ACTION_MOVE:
                if (ev.getPointerCount() == 1)
                {
                    int deltaY = (int) (lastMotionY - curY);
                    lastMotionY = curY;
                    if (deltaY < 0)
                    {
                        if (upExDsts == 0)
                        {
                            if (movDsts > 0)
                            {
                                int thisMov = Math.max(-movDsts, deltaY);
                                movDsts += thisMov;
                                scrollBy(0, thisMov);
                            }
                            else if (movDsts == 0)
                            {
                                dnExDsts -= deltaY / 2;
                                scrollBy(0, deltaY / 2);
                            }
                        }
                        else if (upExDsts > 0)
                        {
                            if (upExDsts >= -deltaY)
                            {
                                upExDsts += deltaY;
                                scrollBy(0, deltaY);
                            }
                            else
                            {
                                upExDsts = 0;
                                scrollBy(0, 0);
                            }
                        }
                    }
                    else if (deltaY > 0)
                    {
                        if (dnExDsts == 0)
                        {
                            if (maxMov - movDsts > 0)
                            {
                                int thisMov = Math.min(maxMov - movDsts, deltaY);
                                movDsts += thisMov;
                                scrollBy(0, thisMov);
                            }
                            else if (maxMov - movDsts == 0)
                            {
                                //if (upExDsts <= 100)
                                //{
                                upExDsts += deltaY / 2;
                                scrollBy(0, deltaY / 2);
                                //}
                            }
                        }
                        else if (dnExDsts > 0)
                        {
                            if (dnExDsts >= deltaY)
                            {
                                dnExDsts -= deltaY;
                                scrollBy(0, deltaY);
                            }
                            else
                            {
                                dnExDsts = 0;
                                scrollBy(0, 0);
                            }
                        }
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                if (upExDsts > 0)
                {
                    scrollBy(0, -upExDsts);
                    invalidate();
                    upExDsts = 0;
                }
                if (dnExDsts > 0)
                {
                    scrollBy(0, dnExDsts);
                    invalidate();
                    dnExDsts = 0;
                }
                //
                vt.computeCurrentVelocity(1000);
                if (upExDsts == 0 && dnExDsts == 0)
                {
                    int vtY = -(int) (vt.getYVelocity() * 10 / 9);
                    sclr.fling(0, movDsts, 0, vtY, 0, 0, 0, maxMov);
                    movDsts = sclr.getFinalY();
                    computeScroll();
                }
                if (vt != null)
                {
                    vt.recycle();
                    vt = null;
                }
            case MotionEvent.ACTION_CANCEL:
                tchStat = TOUCH_STATE_REST;
                break;
        }
        return true;
    }
}
